import { RouterName } from "@/views/router";
import { ChartsService } from "../ChartsService";
import { SimSubwinMsgToSimWin, SubWinName } from "@/engine/types/Win";
import { ChartOpenParam } from "@/engine/types/Socket";
import { ChartSubType, TimeChartUiConfig, TimeLineChartConfigs, TimeSeryChartConfigs, TimelineActivityInitData, TimelineUpdateData, TimelineStatusDrawData, TimelineStatusSery, TimelineStatusUpdate, UrlParam, TimelineTranslateInitDatas, TimelineTranslateInitData } from "@/engine/types/charts";
import * as echarts from "echarts";
import { cloneDeep, throttle } from "lodash";
import { TimeLineChatGetNodeLineDataParam } from "@/engine/SimApi";
import { PropertyKey } from "@/constants/constants";
import { dealDataForChartxAxis } from "@/util/common";

export class TimeLineService {
  CHARTITEMHEIGHT = 45
  CHARTGAP = 20
  xMax = 1000
  xMin = 0
  chartsService: ChartsService;
  chartInstance = {} as echarts.EChartsType;
  color = 'rgba(255, 173, 177, 1 )';
  yValuesModelIdToNameMap = new Map<string, {id: string, name: string}>()
  idToYValuesMap = new Map<string, {label: string, index: number, subIndex?: number}>()
  UiModelConfig = {} as TimeChartUiConfig;
  statusStartPoint = new Map<string, { index: number, label: string, timestamp: number }>()
  statusHasNextPoint = new Map<string, { timestamp: number, label: string, id: string }>() // key::nextId
  timelineRecordInfos = [] as TimelineUpdateData[]

  statusConfig = {
    animation: false,
    title: {
      text: "",
      left: 'center',
      top: 2
    },
    xAxis: [],
    yAxis: [],
    series: [],
    toolbox: {
      show: true,
      feature: {
        saveAsImage: {},
        myExport: {
          show: true,
          title: '',
          icon: "image://statics/icons/app/chart_export.svg",
          onclick: () => {}
        },
      }
    }
  } as any

  stautsDataAll = [
    [
      // {
      //   timestamp1: 0,
      //   label1: 0,
      //   timestamp2: 0,
      //   label2: 0,
      // }
    ]
  ] as any[];

  activityDataAll = [
    // [
    //   [542, 440, '00', true],
    //   [],
    //   [1133, 1133, '02', true],
    //   [785, 785, '03', true],
    //   [2.01, 2, '04', false]
    // ]
  ] as any[];

  activityConfig = {
    title: {
      text: "",
      left: 'center',
      top: 2
    },
    animation: true,
    tooltip: {
      trigger: 'axis',
      axisPointer: {
        type: 'shadow'
      }
    },
    xAxis: [],
    yAxis: [],
    series: [],
    toolbox: {
      show: true,
      right: 10,
      feature: {
        saveAsImage: {
          how: true,
          title: '',
          icon: "image://statics/icons/app/chart_download.svg",
          onclick: () => {}
        },
        myExport: {
          show: true,
          title: '',
          icon: "image://statics/icons/app/chart_export.svg",
          onclick: () => {}
        },
      }
    }
  } as any

  constructor(c: ChartsService) {
    this.chartsService = c;
  }

  private dealYLabel = (l: string) => {
    const step = 5;
    const r = [] as string[];
    const usedLength = Math.ceil(l.length / step);
    for (let i = 0; i < usedLength; i++) {
      r.push(l.substring(i * step, (i + 1) * step));
      if (r.length === 3) {
        if (l.length > 15) {
          r[2] = r[2].substring(0, 3) + '...';
        }
        break;
      }
    }
    return r.join('\n');
  }
  private getActivityChildShapeData(p: any[], api: any, yvalue: any) {
    const offset = 4;
    const r = [];
    let style = api.style({
      stroke: api.visual('color'),
      fill: undefined
    });
    const points = {} as any;
    for (let i = p.length - 3; i >= 0; i--) {
      // filter
      let isContinue = false;
      if (i % 2) {
        isContinue = !((p[i] <= this.xMax && p[i] >= this.xMin) || (p[i - 1] && p[i] < this.xMin && p[i - 1] > this.xMin));
      } else {
        isContinue = !(p[i] <= this.xMax && p[i] >= this.xMin);
      }
      if (isContinue) continue;
      points[i] = api.coord([p[i], yvalue]) as any[];
      if (i % 2) { // left point
        if (p[i] < this.xMin && p[i - 1] > this.xMin) {
          points[i] = api.coord([this.xMin, yvalue]) as any[];
        } else {
          r.push({ // left vertical
            type: 'line',
            transition: ['shape'],
            shape: {
              x1: points[i][0],
              y1: points[i][1] + offset,
              x2: points[i][0],
              y2: points[i][1] - offset,
            },
            style: style
          });
        }
      } else { 
        if (this.xMax < p[i]) {
          points[i] = api.coord([this.xMax, yvalue]) as any[];
        }
        const horizontalLine = { // horizoncal line
          type: 'line',
          transition: ['shape'],
          shape: {
            x1: points[i + 1][0],
            y1: points[i + 1][1],
            x2: points[i][0],
            y2: points[i][1],
          },
          textContent: {
            style: {
              text: `${Math.floor(p[i] - p[i + 1])}ms`,
              fontSize: 12
            }
          },
          textConfig: {
            position: 'top'
          },
          silent: true,
          style: style
        };
        if (!p[p.length - 1] && !i) { // not finish
          horizontalLine.textContent.style.text = '';
        }
        r.push(horizontalLine);
        if ((p[p.length - 1] === true || i) && this.xMax >= p[i]) {
          // add right vertical
          r.push({
            type: 'line',
            transition: ['shape'],
            shape: {
              x1: points[i][0],
              y1: points[i][1] + offset,
              x2: points[i][0],
              y2: points[i][1] - offset,
            },
            style: style
          });
        }
      }
    }
    return r;
  }
  private getStatusChildShapeData(data: TimelineStatusDrawData, api: any) {
    const r = [] as any[];
    let style = api.style({
      stroke: api.visual('color'),
      fill: undefined
    });
    data.sort((a, b) => {
      return a.timestamp1 - b.timestamp1;
    });
    // combine horizontal data
    for (const [i, d] of data.entries()) {
      for (let ii = i + 1; ii < data.length; ii++) {
        if (d.label1 === data[ii].label1 && d.label2 === data[ii].label2 && d.timestamp2 === data[ii].timestamp1) {
          d.timestamp2 = data[ii].timestamp2;
          data.splice(ii, 1);
        }
      }
    }
    for (const l of data) {
      if (this.xMin > l.timestamp2 || this.xMax < l.timestamp1) continue;
      const start = api.coord([l.timestamp1 < this.xMin ? this.xMin : l.timestamp1, l.label1]);
      const end = api.coord([l.timestamp2 > this.xMax ? this.xMax : l.timestamp2, l.label2]);
      const drawLine = {
        type: 'line',
        transition: ['shape'],
        shape: {
          x1: start[0],
          y1: start[1],
          x2: end[0],
          y2: end[1], 
        },
        textContent: {
          style: {
            text: '',
            fontSize: 8
          }
        },
        textConfig: {
          position: 'top'
        },
        silent: true,
        style: style
      };
      if (l.label1 === l.label2 && l.timestamp2 - l.timestamp1) { // horizontal
        drawLine.textContent.style.text = `${Math.floor(l.timestamp2 - l.timestamp1)}ms`;
      }
      r.push(drawLine);
    }
    return r;
  }
  
  serveInit() {
    this.xMax = 1000;
    this.xMin = 0;
  }

  openChartWin(p: ChartOpenParam) {
    let urlParam = {
      ...p,
      type: ChartSubType.TimeLine,
    } as UrlParam;
    if (p.uiId) {
      urlParam = {
        ...urlParam,
        projectId: this.chartsService.controller.projectId,
        modelId: this.chartsService.controller.modelId,
        simulationSessionId: this.chartsService.controller.sessionId,
      };
    }
    this.chartsService.controller.winService.openPluginSubWin(
      RouterName.TimeLine,
      { width: 800, height: 500, skipTaskbar: false, title: SubWinName.Timeline },
      urlParam
    );
  }

  activityTimeLineUpdate(data: TimelineUpdateData) {

    this.timelineRecordInfos.push(data);

    if (this.UiModelConfig[PropertyKey.TimelineChart_FixedTimeLength].value === undefined) { 
      if (data.timestamp > this.xMax) {
        this.xMax = dealDataForChartxAxis(data.timestamp);
      }
    } else {
      if (this.UiModelConfig[PropertyKey.TimeSeriesChart_FixedTimeLocation].value === undefined) {
        if (data.timestamp > this.UiModelConfig[PropertyKey.TimelineChart_FixedTimeLength].value) {
          this.xMax = dealDataForChartxAxis(data.timestamp);
          this.xMin = this.xMax - this.UiModelConfig[PropertyKey.TimelineChart_FixedTimeLength].value;
        }
      }
    }

    for (const x of this.activityConfig.xAxis) {
      x.max = this.xMax;
      if (this.xMin) {
        x.min = this.xMin;
      }
      x.interval = this.xMax / 5;
    }

    const yMapInfo = this.idToYValuesMap.get(data.id);
    if (yMapInfo) {
      // ensure not empty
      if (yMapInfo.subIndex) {
        for (let i = 0; i <= yMapInfo.subIndex; i++) {
          if (!this.activityDataAll[yMapInfo.index][i]) {
            this.activityDataAll[yMapInfo.index][i] = [];
          }
        }
      }
      // update not-end
      for (const e of this.activityDataAll[yMapInfo.index]) {
        if (e.length && !e[e.length - 1]) {
          e.splice(0, 1, data.timestamp);
        }
      }

      const willUpdateElement = this.activityDataAll[yMapInfo.index][yMapInfo.subIndex!];
      if (!willUpdateElement.length) { // add first data
        willUpdateElement.push(data.timestamp + 0.01, data.timestamp, yMapInfo.label, false);
      } else {
        if (!willUpdateElement[willUpdateElement.length - 1]) {
          // replace first element
          willUpdateElement.splice(0, 1, data.timestamp);
        } else {
          willUpdateElement.unshift(data.timestamp + 0.01, data.timestamp);
        }
        willUpdateElement[willUpdateElement.length - 1] = !willUpdateElement[willUpdateElement.length - 1];
      }
    }
    this.chartRender(this.activityConfig);
  }

  statusTimeLineUpdate(data: TimelineUpdateData) {

    this.timelineRecordInfos.push(data);
    if (this.UiModelConfig[PropertyKey.TimelineChart_FixedTimeLength].value === undefined) { 
      if (data.timestamp > this.xMax) {
        this.xMax = dealDataForChartxAxis(data.timestamp);
      }
    } else {
      if (this.UiModelConfig[PropertyKey.TimeSeriesChart_FixedTimeLocation].value === undefined) {
        if (data.timestamp > this.UiModelConfig[PropertyKey.TimelineChart_FixedTimeLength].value) {
          this.xMax = dealDataForChartxAxis(data.timestamp);
          this.xMin = this.xMax - this.UiModelConfig[PropertyKey.TimelineChart_FixedTimeLength].value;
        }
      }
    }

    for (const x of this.statusConfig.xAxis) {
      x.max = this.xMax;
      if (this.xMin) {
        x.min = this.xMin;
      }
      x.interval = this.xMax / 5;
    }

    const yMapInfo = this.idToYValuesMap.get(data.id);
    if (yMapInfo) {
      const isStartExit = this.statusStartPoint.get(data.id);
      if (isStartExit) {
        this.stautsDataAll[yMapInfo.index].push({
          timestamp1: isStartExit.timestamp,
          label1: isStartExit.label,
          timestamp2: data.timestamp,
          label2: yMapInfo.label
        });
        if (data.nextId) {
          for (const nextId of data.nextId.split(',')) {
            this.statusHasNextPoint.set(nextId, {
              timestamp: data.timestamp,
              label: yMapInfo.label,
              id: data.id
            });
            // del start point
            this.statusStartPoint.delete(nextId);
          }
        }
      }
      this.statusStartPoint.set(data.id, { timestamp: data.timestamp, index: yMapInfo.index, label: yMapInfo.label });

      const isNextExit = this.statusHasNextPoint.get(data.id);
      if (isNextExit) {
        this.stautsDataAll[yMapInfo.index].push(
          {
            timestamp1: isNextExit.timestamp,
            label1: isNextExit.label,
            timestamp2: data.timestamp,
            label2: isNextExit.label
          },
          {
            timestamp1: data.timestamp,
            label1: isNextExit.label,
            timestamp2: data.timestamp,
            label2: yMapInfo.label
          }
        );
        this.statusStartPoint.delete(isNextExit.id);
        this.statusHasNextPoint.delete(data.id);
        this.statusStartPoint.set(data.id, { timestamp: data.timestamp, index: yMapInfo.index, label: yMapInfo.label });
        
      }
    }
    this.chartRender(this.statusConfig);
  }

  getTimeLineChatGetNodeLineDataParam(p: TimeChartUiConfig) {
    const apiP = {} as TimeLineChatGetNodeLineDataParam;
    apiP.simulationSessionId = this.chartsService.wparam.simulationSessionId!;
    apiP.representsId = p[PropertyKey.SelectPropertiesConfig_represents].value as string;
    apiP.timelineMode = p[PropertyKey.TimelineChart_TimelineMode].value!.includes('state') ? 'state' : 'activity';
    apiP.contextPlot = p[PropertyKey.TimelineChart_ContextPlot].value;
    apiP.ignoredElementsId = p[PropertyKey.TimelineChart_IgnoredElements]?.value.join(',');
    return apiP;
  }

  dealStatusInitDataAndRender(data: TimelineActivityInitData, uiModelConfig: TimeChartUiConfig) {
    const ignoreElements = [] as string[];
    const translateInitDatas = [];
    let totalCount = 0;
    for (const d of data) {
      const subTranslateInitDatas = [];
      if (d.nodeDataList && d.nodeDataList.length) {
        for (const v of d.nodeDataList) {
          if (!ignoreElements.includes(v.modelId)) {
            totalCount++;
            subTranslateInitDatas.unshift({ nodeId: v.nodeId, nodeName: v.nodeName });
          }
        }
      }
      translateInitDatas.push(subTranslateInitDatas);
    }
    const grids = [];
    const yAxiss = [];
    const xAxiss = [];
    const seryss = [];
    let passedItemNumber = 0;
    
    for (const [index, data] of translateInitDatas.entries()) {
      grids.push(this.createTimeLineGridData(data, index, passedItemNumber));
      yAxiss.push(this.createTimeLineYAxisData(data, index));
      xAxiss.push(this.createTimeLineXAxisData(index, (index + 1) === translateInitDatas.length));
      seryss.push(this.createStatusSeryData(index));
      passedItemNumber += data.length;
    }
    this.statusConfig.grid = grids;
    this.statusConfig.xAxis = xAxiss;
    this.statusConfig.yAxis = yAxiss;
    this.statusConfig.series = seryss;
    this.statusConfig.toolbox.feature.myExport.title = this.chartsService.controller.locale.localeTrans('ex_csv_table');
    this.statusConfig.toolbox.feature.saveAsImage.title = this.chartsService.controller.locale.localeTrans('_save_as_picture');
    this.statusConfig.toolbox.feature.myExport.onclick = this.exportCSV.bind(this);
    this.chartRender(this.statusConfig, { height: this.getChartHeight(totalCount, translateInitDatas.length) });
  }

  dealActivityInitDataAndRender(data: TimelineActivityInitData, uiModelConfig: TimeChartUiConfig) {
    let translateInitDatas = [] as TimelineTranslateInitDatas;
    let totalCount = 0;
    for (const d of data) {
      const subTranslateInitDatas = {
        name: '',
        data: [] as TimelineTranslateInitData[],
      };
      totalCount++;
      subTranslateInitDatas.name = d.ownerName;
      subTranslateInitDatas.data.unshift({ nodeId: d.behaviorId, nodeName: d.behaviorName });
      if (d.nodeDataList && d.nodeDataList.length) {
        for (const v of d.nodeDataList) {
          totalCount++;
          subTranslateInitDatas.data.unshift({ nodeId: v.nodeId, nodeName: v.nodeName });
        }
      }
      translateInitDatas.unshift(subTranslateInitDatas);
    }
    const grids = [];
    const yAxiss = [];
    const xAxiss = [];
    const seryss = [];
    let passedItemNumber = 0;
    for (const [index, tdata] of translateInitDatas.entries()) {
      grids.push(this.createTimeLineGridData(tdata.data, index, passedItemNumber));
      yAxiss.push(this.createTimeLineYAxisData(tdata.data, index, tdata.name));
      xAxiss.push(this.createTimeLineXAxisData(index, (index + 1) === translateInitDatas.length));
      seryss.push(this.createActivitySeryData(index));
      passedItemNumber += tdata.data.length;
    }
    this.activityConfig.grid = grids;
    this.activityConfig.xAxis = xAxiss;
    this.activityConfig.yAxis = yAxiss;
    this.activityConfig.series = seryss;
    this.activityConfig.toolbox.feature.myExport.title = this.chartsService.controller.locale.localeTrans('ex_csv_table');
    this.activityConfig.toolbox.feature.saveAsImage.title = this.chartsService.controller.locale.localeTrans('_save_as_picture');
    this.activityConfig.toolbox.feature.myExport.onclick = this.exportCSV.bind(this);
    this.chartRender(this.activityConfig, { height: this.getChartHeight(totalCount, translateInitDatas.length) });
  }

  private getChartHeight(total: number, groups: number) {
    const bottomOffset = 50;
    return this.CHARTITEMHEIGHT * (total + 1) + (groups - 1) * this.CHARTGAP + bottomOffset;
  }

  exportCSV() {
    const headers = ['Name', 'Start time', 'End time', 'Duration(ms)'];
    const csvDatas = [headers];
    csvDatas.push(...this.dealTimelineRecordInfosToCsv());
    this.chartsService.controller.localFileService.saveDataToCSV(csvDatas);
  }

  dealTimelineRecordInfosToCsv() {
    const idToLineData = new Map<string, {name: string, timestamp: number}>(); // key simId
    const r = [] as any[];
    for (const d of this.timelineRecordInfos) {
      const isStart = idToLineData.get(d.id);
      if (!isStart) {
        idToLineData.set(d.id, { timestamp: d.timestamp, name: d.modelName });
      } else {
        r.push([isStart.name, isStart.timestamp, d.timestamp, d.timestamp - isStart.timestamp]);
        idToLineData.delete(d.id);
      }
    }
    return r;
  }

  createStatusSeryData(index: number) {
    this.stautsDataAll.push([]);
    return {
      seriesId: index,
      type: 'custom',
      name: 'sery-name-status',
      itemStyle: {
        borderWidth: 1.5,
        color: 'red',
      },
      yAxisIndex: index,
      xAxisIndex: index,
      renderItem: (params: any, api: any) => {
        const usedData = this.stautsDataAll[params.seriesIndex] as [];
        let childShapeData = [] as any[];
        if (usedData.length) {
          childShapeData = this.getStatusChildShapeData(usedData, api);
        }
        return {
          type: 'group',
          children: [
            ...childShapeData
          ]
        };
      },
      data: this.stautsDataAll[index]
    };
  }

  createActivitySeryData(index: number) {
    this.activityDataAll.push([]);
    return {
      seriesId: index,
      type: 'custom',
      name: 'sery-name',
      itemStyle: {
        borderWidth: 1.5,
        color: 'red',
      },
      yAxisIndex: index,
      xAxisIndex: index,
      renderItem: (params: any, api: any) => {
        const usedData = this.activityDataAll[params.seriesIndex][params.dataIndex] as [];
        let childShapeData = [] as any[];
        if (usedData.length) {
          childShapeData = this.getActivityChildShapeData(usedData, api, usedData[usedData.length - 2]);
        }
        return {
          type: 'group',
          children: [
            ...childShapeData
          ]
        };
      },
      data: this.activityDataAll[index]
    };
  }

  createTimeLineGridData(data: TimelineTranslateInitData[], index: number, passedItem: number) {
    return { left: `60px`, right: `40px`, top: `${passedItem * this.CHARTITEMHEIGHT + this.CHARTITEMHEIGHT + index * this.CHARTGAP}px`, height: `${data.length * this.CHARTITEMHEIGHT }px` };
  }

  createTimeLineXAxisData(index: number, isEnd: boolean) {
    const r = {
      gridIndex: index,
      type: 'value',
      min: this.xMin,
      max: this.xMax,
      splitLine: {
        show: true
      },
      axisLabel: {
        show: false
      },
      interval: this.xMax / 5
    };
    if (isEnd) {
      r.axisLabel.show = true;
    }
    return r;
  }

  createTimeLineYAxisData(data: TimelineTranslateInitData[], index: number, name = '') {
    const ylabel = [];
    for (const [i, d] of data.entries()) {
      ylabel.push(`${index}${i}`);
      this.yValuesModelIdToNameMap.set(`${index}${i}`, { id: d.nodeId, name: d.nodeName });
      this.idToYValuesMap.set(d.nodeId, { label: `${index}${i}`, index: index, subIndex: i });
    }
    return {
      name,
      nameLocation: 'center',
      nameGap: 35,
      gridIndex: index,
      type: 'category',
      axisLabel: {
        fontSize: 8,
        formatter: (value: string) => {
          const yValuesModelIdToNameMapData = this.yValuesModelIdToNameMap.get(value);
          if (yValuesModelIdToNameMapData) {
            return this.dealYLabel(yValuesModelIdToNameMapData.name);
          }
        },
        margin: 2
      },
      nameTextStyle: {
        align: 'center',
        fontWeight: 'bold',
        verticalAlign: 'center',
        fontSize: 10,
      },
      nameTruncate: {
        maxWidth: (this.CHARTITEMHEIGHT - 5) * data.length
      },
      data: ylabel,
      axisTick: {
        show: false
      },
      splitLine: {
        show: true
      },
    };
  }

  async init(p: UrlParam) {
    this.serveInit();
    this.UiModelConfig = (await this.chartsService.controller.modelApi.modelPropertyGetPropertyValuesById({
      projectId: p.projectId!,
      modelId: p.uiId!,
      propertyKeys: TimeLineChartConfigs
    })).data?.data;
    if (this.UiModelConfig) {
      // UiModelConfig send to sim-main-frame
      if (this.UiModelConfig[PropertyKey.TimeSeriesChart_KeepOpenAfterTermination]?.value) {
        this.chartsService.controller.winService.simSubWinSendInfoToSimWin({ type: SimSubwinMsgToSimWin.TimeLineKeepOpen });
      }
      if (this.UiModelConfig[PropertyKey.TimelineChart_FixedTimeLength]?.value) {
        this.xMax = this.UiModelConfig[PropertyKey.TimelineChart_FixedTimeLength].value;
        if (this.UiModelConfig[PropertyKey.TimeSeriesChart_FixedTimeLocation]?.value) {
          this.xMax = this.UiModelConfig[PropertyKey.TimelineChart_FixedTimeLength].value + this.UiModelConfig[PropertyKey.TimeSeriesChart_FixedTimeLocation]?.value;
        }
      }
      const timeLineChatGetNodeLineDataParam = this.getTimeLineChatGetNodeLineDataParam(this.UiModelConfig);
      const timeInitData = (await this.chartsService.controller.simApi.timeLineChatGetNodeLineData(timeLineChatGetNodeLineDataParam)).data?.data;
      if (timeInitData) {
        if (timeLineChatGetNodeLineDataParam.timelineMode === 'activity') {
          this.activityConfig.title.text = this.UiModelConfig[PropertyKey.TimeSeriesChart_Title].value;
          this.dealActivityInitDataAndRender(timeInitData, this.UiModelConfig);
        } else if (timeLineChatGetNodeLineDataParam.timelineMode === 'state') {
          this.statusConfig.title.text = this.UiModelConfig[PropertyKey.TimeSeriesChart_Title].value;
          this.dealStatusInitDataAndRender(timeInitData, this.UiModelConfig);
        }
      }
    }
  }

  chartRender(config: any, boxstyle?: {height: number}) {
    if (Object.keys(this.chartInstance).length) {
      this.chartInstance.setOption(config);
    } else {
      const chartBox = document.getElementById('box_id');
      if (!chartBox) return;
      if (boxstyle) {
        chartBox.style.height = `${boxstyle.height}px`;
      }
      const timelineChart = this.chartInstance = echarts.init(chartBox, boxstyle);
      timelineChart.setOption(config);
      window.addEventListener('resize', throttle(() => timelineChart.resize()));
    }
    
  }
}